package king.dominic.jlibrary.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Created by DominicKing on 2017/9/5.
 * Reflect
 */

public class Ref {


    public static <T> T gField(Object obj, String name, Class<T> clazz) {
        return getField(obj.getClass(), obj, name, clazz);
    }

    public static <T, OBJECT extends CLASS, CLASS> T gField(Class<CLASS> objClass, OBJECT obj, String name, Class<T> fieldClass) {
        return getField(objClass, obj, name, fieldClass);
    }

    public static boolean sField(Object obj, String name, Object setValue) {
        return setField(obj.getClass(), obj, name, setValue);
    }

    public static <OBJECT extends CLASS, CLASS> boolean sField(Class<CLASS> clazz, OBJECT obj, String name, Object value) {
        return setField(clazz, obj, name, value);
    }

    public static Object cMethod(Object obj, String name, Object... params) {
        return callMethod(obj.getClass(), obj, name, params);
    }

    public static <OBJECT extends CLASS, CLASS> Object cMethod(Class<CLASS> objClass, OBJECT obj, String name, Object... params) {
        return callMethod(objClass, obj, name, params);
    }

    public static ArrayList<String> qFieldD(Class clazz) {
        return queryDeclaredField(clazz);
    }

    public static ArrayList<String> qField(Class clazz) {
        return queryField(clazz);
    }

    public static <CLASS> Object cStaticMethod(Class<CLASS> objClass, String name, Object... params) {
        return callMethod(objClass, null, name, params);
    }
    /*
    ==================================================================================================================================
    ==================================================================================================================================
     */

    private static ArrayList<String> queryDeclaredField(Class objClass) {
        Field[] fields = objClass.getDeclaredFields();
        if (fields == null) return null;
        ArrayList<String> array = new ArrayList<>();
        for (Field field : fields) {
            field.setAccessible(true);
            array.add(field.getName());
        }
        return array;
    }

    private static ArrayList<String> queryField(Class objClass) {
        Field[] fields = objClass.getFields();
        if (fields == null) return null;
        ArrayList<String> array = new ArrayList<>();
        for (Field field : fields) {
            field.setAccessible(true);
            array.add(field.getName());
        }
        return array;
    }

    @SuppressWarnings({"TryWithIdenticalCatches", "unchecked"})
    private static <T> T getField(Class objClass, Object obj, String name, Class<T> fieldClass) {
        try {
            Field f = objClass.getDeclaredField(name);
            if (f == null) return null;
            f.setAccessible(true);

            return (T) f.get(obj);
        } catch (NoSuchFieldException e) {
            return getField2(objClass, obj, name, fieldClass);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings({"TryWithIdenticalCatches", "unchecked"})
    private static <T> T getField2(Class objClass, Object obj, String name, Class<T> fieldClass) {
        try {
            Field f = objClass.getField(name);
            if (f == null) return null;
            f.setAccessible(true);

            return (T) f.get(obj);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings("TryWithIdenticalCatches")
    private static boolean setField(Class clazz, Object obj, String name, Object setValue) {
        try {
            Field f = clazz.getDeclaredField(name);
            if (f == null) return false;
            f.setAccessible(true);

            f.set(obj, setValue);
            return true;
        } catch (NoSuchFieldException e) {
            return setField2(clazz, obj, name, setValue);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }

    @SuppressWarnings("TryWithIdenticalCatches")
    private static boolean setField2(Class clazz, Object obj, String name, Object setValue) {
        try {
            Field f = clazz.getField(name);
            if (f == null) return false;
            f.setAccessible(true);

            f.set(obj, setValue);
            return true;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }

    @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
    private static Object callMethod(Class objClass, Object obj, String name, Object... params) {
        Class<?>[] classes = null;
        if (params != null) {
            classes = new Class[params.length];
            int i = 0;
            for (Object o : params) {
                classes[i++] = o.getClass();
            }
        }
        try {
            Method m = objClass.getDeclaredMethod(name, classes);
            m.setAccessible(true);
            return m.invoke(obj, params);
        } catch (NoSuchMethodException e) {
            return callMethod2(objClass, obj, name, params);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
    private static Object callMethod2(Class objClass, Object obj, String name, Object... params) {
        Class<?>[] classes = null;
        if (params != null) {
            classes = new Class[params.length];
            int i = 0;
            for (Object o : params) {
                classes[i++] = o.getClass();
            }
        }
        try {
            Method m = objClass.getMethod(name, classes);
            m.setAccessible(true);
            return m.invoke(obj, params);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}
